﻿using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using OfficeOpenXml;
using OfficeOpenXml.Table;

namespace Devonline.AspNetCore;

/// <summary>
/// excel 导出操作的服务
/// </summary>
public class ExcelExportService : IExcelExportService
{
    protected readonly ILogger<ExcelExportService> _logger;
    protected readonly ExcelPackage _excelPackage;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="context"></param>
    /// <param name="logger"></param>
    /// <param name="cache"></param>
    /// <param name="httpContextAccessor"></param>
    /// <param name="appSetting"></param>
    public ExcelExportService(ILogger<ExcelExportService> logger)
    {
        _logger = logger;
        _excelPackage = new ExcelPackage();
    }

    /// <summary>
    /// 导出数据到 excel
    /// 需要 Data(必须), Columns(非必须) SheetName(非必须) 和 Converters(非必须) 参数
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="excelData">excel 数据对象</param>
    public virtual IExcelExportService Export<TEntitySet>(ExcelData<TEntitySet> excelData)
    {
        if (excelData.Data == null || (!excelData.Data.Any()))
        {
            throw new Exception($"excel 文件导出失败, 没有提供导出的数据!");
        }

        var attributes = excelData.GetAttributes();
        if (attributes == null || (!attributes.Any()))
        {
            throw new Exception($"excel 文件导出失败, 没有配置可以导入的列!");
        }

        //转换原数据
        ConvertData(excelData);

        excelData.SheetName ??= typeof(TEntitySet).GetDisplayName();
        using var sheet = _excelPackage.Workbook.Worksheets.Add(excelData.SheetName);
        sheet.Cells.Style.ShrinkToFit = true;

        //load data, TODO TBC 尚不能自动导出引用字段的值
        var members = attributes.OrderBy(x => x.Index).Select(x => x.Field).ToArray();
        sheet.Cells.LoadFromCollection(excelData.Data, true, TableStyles.None, BindingFlags.Public | BindingFlags.Instance, members);

        //格式化列
        FormatColumn(excelData, sheet);

        return this;
    }
    /// <summary>
    /// 导出数据到 excel, 此方法会按默认参数构造 ExcelData 并导出
    /// 需要 Data(必须), Columns(非必须) 和 SheetName(非必须) 参数
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="Data">要导出的数据集合</param>
    public virtual IExcelExportService Export<TEntitySet>(IEnumerable<TEntitySet> Data) => Export(new ExcelData<TEntitySet> { Data = Data });
    /// <summary>
    /// 单个数据集直接导出到一个 excel 文件, 并直接返回
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="excelData">excel 数据对象</param>
    /// <returns></returns>
    public virtual async Task<IActionResult> ExportAsync<TEntitySet>(ExcelData<TEntitySet> excelData)
    {
        Export(excelData);
        var fileName = string.IsNullOrWhiteSpace(excelData.FileName) ? excelData.SheetName + AppSettings.DEFAULT_EXCEL_FILE_EXTENSION : Path.GetFileName(excelData.FileName);
        var fileContents = await _excelPackage.GetAsByteArrayAsync().ConfigureAwait(false);
        return new FileContentResult(fileContents, AppSettings.CONTENT_TYPE_OF_EXCEL) { FileDownloadName = fileName };
    }
    /// <summary>
    /// 导出数据到 excel, 此方法会按默认参数构造 ExcelData 并导出
    /// 需要 Data(必须), Columns(非必须) 和 SheetName(非必须) 参数
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="Data">要导出的数据集合</param>
    public virtual async Task<IActionResult> ExportAsync<TEntitySet>(IEnumerable<TEntitySet> Data) => await ExportAsync(new ExcelData<TEntitySet> { Data = Data });

    /// <summary>
    /// 在存在 Converters 的情况下对即将导出的值进行转换
    /// </summary>
    /// <typeparam name="TEntitySet"></typeparam>
    /// <param name="excelData"></param>
    private void ConvertData<TEntitySet>(ExcelData<TEntitySet> excelData)
    {
        if (excelData.Data != null && excelData.Data.Any() && excelData.Columns != null && excelData.Columns.Any() && excelData.Converters != null && excelData.Converters.Count > 0)
        {
            var type = typeof(TEntitySet);
            foreach (var column in excelData.Columns.Where(x => excelData.Converters.Keys.Contains(x)))
            {
                var propertyInfo = type.GetProperty(column);
                var converter = excelData.Converters[column];
                foreach (var entitySet in excelData.Data)
                {
                    converter(entitySet, propertyInfo?.GetValue(entitySet));
                }
            }
        }
    }
    /// <summary>
    /// 格式化列
    /// </summary>
    /// <typeparam name="TEntitySet"></typeparam>
    /// <param name="excelData"></param>
    /// <exception cref="Exception"></exception>
    private void FormatColumn<TEntitySet>(ExcelData<TEntitySet> excelData, in ExcelWorksheet sheet)
    {
        var rowCount = (excelData.Data?.Count() ?? AppSettings.UNIT_ZERO) + AppSettings.UNIT_ONE;
        foreach (var attribute in excelData.Attributes)
        {
            try
            {
                //set width
                sheet.Column(attribute.Index).Width = attribute.Size;

                //set format
                if (rowCount > AppSettings.UNIT_ONE && (!string.IsNullOrWhiteSpace(attribute.Format)))
                {
                    sheet.Cells[AppSettings.UNIT_TWO, attribute.Index, rowCount, attribute.Index].Style.Numberformat.Format = attribute.Format;
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Excel sheet {excelSheet} column {excelColumn} set failed!", excelData.SheetName, attribute.Name);
            }
        }
    }
}